---
title: Runtime Layer
---

assistant-ui components are full stack components. This means that they include both the UI presentation, but also logic to communicate with an external system. This logic is handled by the runtime layer and APIs.

You interact with the runtime layer in two ways:

- defining a runtime for your app
- using the runtime APIs to interact with the runtime

## Defining a runtime

assistant-ui ships with two low-level runtimes:

- `useLocalRuntime`
- `useExternalStoreRuntime`

Both of these runtimes let you implement your own runtime. The conceptual difference between the two is that `useLocalRuntime` takes ownership of the data layer, while `useExternalStoreRuntime` does not.

If you have a stateful API to integrate, use `useExternalStoreRuntime`, if you have a stateless API to integrate, use `useLocalRuntime`.

### Higher level runtimes

For many services and APIs, assistant-ui provides deeper integrations. These are built with the two low-level runtimes mentioned above.

- `useChatRuntime`: Connect to Vercel AI SDK backends
- `useVercelUseChatRuntime`: Integrate with Vercel AI SDK's `useChat` hook
- `useVercelUseAssistantRuntime`: Integrate with Vercel AI SDK's `useAssistant` hook (OpenAI Assistants API)
- `useVercelRSCRuntime`: Integrate with Vercel AI SDK React Server Components
- `useLangGraphRuntime`: Connect to LangGraph Cloud
- ...

### Runtime Providers

The following components accept a `runtime` prop:

- `AssistantRuntimeProvider`
- `Thread`

These components put the Runtime in the React Context, so that all child components can access the runtime.

### Runtime Adapters

Most runtimes accept additional adapters to configure extra integrations:

- ChatModelAdapter: Configures the backend API
- AttachmentAdapter: Configures the file/media attachment API
- SpeechSynthesisAdapter: Configures the speech API
- FeedbackAdapter: Configures the feedback API
- SuggestionAdapter: Configures dynamic suggestion generation based on conversation context

## Using the runtime APIs

The same API used by the assistant-ui components is also available to you. This allows you to build your own UI components and integrate them with the runtime layer.

### Runtime Hierarchy

The runtime API is nested as such:

import { File, Folder, Files } from "fumadocs-ui/components/files";

<Files>
  <Folder name="AssistantRuntime" defaultOpen>
    <Folder name="ThreadListRuntime" defaultOpen>
      <Folder name="ThreadRuntime" defaultOpen>
        <Folder name="MessageRuntime" defaultOpen>
          <Folder
            name="MessagePartRuntime (Text / Reasoning / Image / Audio / Tool-Call / UI)"
            defaultOpen
          ></Folder>
          <Folder name="MessageAttachmentRuntime" defaultOpen></Folder>
          <Folder name="EditComposerRuntime" defaultOpen>
            <Folder name="EditComposerAttachmentRuntime" defaultOpen></Folder>
          </Folder>
        </Folder>
        <Folder name="ThreadComposerRuntime" defaultOpen>
          <Folder name="ThreadComposerAttachmentRuntime" defaultOpen></Folder>
        </Folder>
      </Folder>
    </Folder>
  </Folder>
</Files>

The AssistantRuntime (which encompasses everything), is sometimes simply called `Runtime`.

### Runtime Context Provider Components

The following components provide the runtime APIs:

```tsx
// provides AssistantRuntime, ThreadListRuntime, ThreadRuntime, ComposerRuntime (ThreadComposer)
<AssistantRuntimeProvider runtime={runtime} />

// renders every message, provides MessageRuntime, ComposerRuntime (EditComposer)
<ThreadPrimitive.Messages components={{ Message, ... }} />

// renders every message part, provides MessagePartRuntime
<MessagePrimitive.Parts components={{ Text, Reasoning, Image, Audio, UI, tools }} />

// renders every attachment, provides AttachmentRuntime (Thread or EditComposer)
<ComposerPrimitive.Attachments components={{ Attachment, ... }} />

// renders every attachment, provides AtatchmentRuntime (Message)
<MessagePrimitive.Attachments components={{ Attachment, ... }} />

// provides a custom TextMessagePartRuntime
<TextMessagePartProvider text="Hello!" />
```

### Accessing runtime APIs

You can access the runtime APIs with react hooks:

```tsx
const runtime = useAssistantRuntime();
const threadRuntime = useThreadRuntime();
const messageRuntime = useMessageRuntime();
const MessagePartRuntime = useMessagePartRuntime();

// thread manager has no separate hook (1:1 relationship with assistant runtime)
const ThreadListRuntime = useAssistantRuntime().threads;

// composer runtime is multi-use
const composerRuntime = useComposerRuntime(); // refers to edit composer if available, otherwise thread composer

// thread manager has no separate hook (1:1 relationship with assistant runtime)
const threadComposer = useThreadRuntime().composer;

// thread manager has no separate hook (1:1 relationship with assistant runtime)
const editComposerRuntime = useMessageRuntime().composer;

// attachment runtime is multi-use
const attachmentRuntime = useAttachmentRuntime(); // refers to the closest attachment runtime
const threadComposerAttachmentRuntime = useThreadComposerAttachmentRuntime();
const editComposerAttachmentRuntime = useEditComposerAttachmentRuntime();
const messageAttachmentRuntime = useMessageAttachmentRuntime();
```

### Accessing runtime state

Most runtimes also expose a state through two methods `getState` and `subscribe`. The following helper hooks subscribe to the state, so that your component is updated when the state changes:

```tsx
useThreadList(); // get thread manager state
useThread(); // get thread state
useMessage(); // get message state
useMessagePart(); // get message part state
useComposer(); // get composer state
useThreadComposer(); // get thread composer state
useEditComposer(); // get edit composer state
useAttachment(); // get attachment state
useThreadComposerAttachment(); // get thread composer attachment state
useEditComposerAttachment(); // get edit composer attachment state
useMessageAttachment(); // get message attachment state
```

You might not want to subscribe to evey update. In that case, pass a callback selector to the hook:

```tsx
// only subscribe to role changes
const role = useMessage((state) => message.role);
```
